﻿using System;
using System.IO;
using System.Net;
using System.Text;
using System.Text.RegularExpressions;
using System.Web;
using System.Xml;
using Dexter.Dependency;
using Dexter.Logger.Contracts;
using Dexter.Services;

namespace Dexter.Web.HttpHandlers {
	/// <summary>
	/// This handlers rapresents the service gateway for item pingback.
	/// </summary>
	public class PingbackHandler : HttpHandlerBase {
		const string SUCCESS = "<methodResponse><params><param><value><string>PingBack added, Thanks!</string></value></param></params></methodResponse>";
		static readonly Regex regexTitle = new Regex ( @"<title.*?>(?<title>.*)</title>" , RegexOptions.IgnoreCase | RegexOptions.Compiled );
		readonly ILogger logger;
		readonly IPostService postService;
		readonly ITrackBackService trackBacService;

		string html;
		bool sourceHasLink;
		string title;


		/// <summary>
		/// 	Initializes a new instance of the <see cref = "PingbackHandler" /> class.
		/// </summary>
		public PingbackHandler ( ) {
			this.logger = DexterContainer.Resolve <ILogger> ( );
			this.postService = DexterContainer.Resolve <IPostService> ( );
			this.trackBacService = DexterContainer.Resolve<ITrackBackService> ( );
		}

		/// <summary>
		/// Initializes a new instance of the <see cref="PingbackHandler"/> class.
		/// </summary>
		/// <param name="siteConfigurationService">The site configuration service.</param>
		/// <param name="logger">The logger.</param>
		/// <param name="postService">The post service.</param>
		/// <param name="trackBackService">The track back service.</param>
		public PingbackHandler ( IConfigurationService siteConfigurationService , ILogger logger , IPostService postService, ITrackBackService trackBackService ) : base ( siteConfigurationService ) {
			this.logger = logger;
			this.postService = postService;
			this.trackBacService = trackBackService;
		}

		/// <summary>
		/// 	Enables processing of HTTP Web requests by a custom HttpHandler that implements the <see cref = "T:System.Web.IHttpHandler" /> interface.
		/// </summary>
		/// <param name = "context">An <see cref = "T:System.Web.HttpContext" /> object that provides references to the intrinsic server objects (for example, Request, Response, Session, and Server) used to service HTTP requests.</param>
		public override void ProcessRequest ( HttpContextBase context ) {
			if ( !base.SiteConfigurationService.Configuration.Tracking.EnablePingBackReceive ) {
				logger.Debug ( "PingBack Receive is disabled, returning 404." );
				throw new HttpException ( 404 , "File Not Found." );
			}

			XmlDocument doc = RetrieveXmlDocument ( context );
			XmlNodeList list = doc.SelectNodes ( "methodCall/params/param/value/string" ) ?? doc.SelectNodes ( "methodCall/params/param/value" );

			if ( list != null ) {
				string sourceUrl = list[ 0 ].InnerText.Trim ( );
				string targetUrl = list[ 1 ].InnerText.Trim ( );

				ExamineSourcePage ( sourceUrl , targetUrl );

				if ( !sourceHasLink ) {
					throw new HttpException ( 404 , "File Not Found." );
				}

				context.Response.ContentType = "text/xml";

				Uri url = new Uri ( targetUrl );

				string postUrl = url.Segments[ url.Segments.Length - 1 ];

				var post = postService.GetBySlug ( postUrl );

				if ( post != null ) {
					bool back = trackBacService.IsFirstTrackbackBack ( post.Id , new Uri ( sourceUrl ) );

					if ( back ) {
						trackBacService.AddTrackBack ( post.Id , new Uri ( sourceUrl ) , title );
						context.Response.Write ( SUCCESS );
					}
					else {
						SendError ( context , 48 , "The pingback has already been registered." );
					}
				}
			}
		}

		static XmlDocument RetrieveXmlDocument ( HttpContextBase context ) {
			string xml = ParseRequest ( context );
			if ( !xml.Contains ( "<methodName>pingback.ping</methodName>" ) ) {
				throw new HttpException ( 404 , "File Not Found." );
			}

			XmlDocument doc = new XmlDocument ( );
			doc.LoadXml ( xml );

			return doc;
		}

		static string ParseRequest ( HttpContextBase context ) {
			byte[] buffer = new byte[context.Request.InputStream.Length];
			context.Request.InputStream.Read ( buffer , 0 , buffer.Length );

			return Encoding.Default.GetString ( buffer );
		}

		void ExamineSourcePage ( string sourceUrl , string targetUrl ) {
			try {
				HttpWebRequest request = ( HttpWebRequest ) WebRequest.Create ( sourceUrl );

				//Simulo uno useragent xchè alcuni siti bloccano le chiamate senza agent
				request.UserAgent = "Dexter/0.5 Beta (.NET CLR 3.5.30729)";
				request.Credentials = CredentialCache.DefaultNetworkCredentials;
				request.MaximumAutomaticRedirections = 4;
				using ( HttpWebResponse response = ( HttpWebResponse ) request.GetResponse ( ) ) {
					// Get the stream associated with the response.
					using ( Stream receiveStream = response.GetResponseStream ( ) ) {
						// Pipes the stream to a higher level stream reader with the required encoding format. 
						if ( receiveStream != null ) {
							using ( StreamReader readStream = new StreamReader ( receiveStream , Encoding.UTF8 ) ) {
								html = readStream.ReadToEnd ( );
								Match match = regexTitle.Match ( html );

								if ( match.Success ) {
									title = match.Groups[ "title" ].Value
										.Trim ( )
										.Replace ( @"\r" , string.Empty )
										.Replace ( @"\n" , string.Empty );

									sourceHasLink = html.ToUpperInvariant ( ).Contains ( targetUrl.ToUpperInvariant ( ) );
								}
							}
						}
					}
				}
			}
			catch ( Exception exc ) {
				logger.Error ( exc.Message , exc );
				sourceHasLink = false;
			}
		}

		static void SendError ( HttpContextBase context , int code , string message ) {
			StringBuilder sb = new StringBuilder ( );
			sb.Append ( "<?xml version=\"1.0\"?>" );
			sb.Append ( "<methodResponse>" );
			sb.Append ( "<fault>" );
			sb.Append ( "<value>" );
			sb.Append ( "<struct>" );
			sb.Append ( "<member>" );
			sb.Append ( "<name>faultCode</name>" );
			sb.AppendFormat ( "<value><int>{0}</int></value>" , code );
			sb.Append ( "</member>" );
			sb.Append ( "<member>" );
			sb.Append ( "<name>faultString</name>" );
			sb.AppendFormat ( "<value><string>{0}</string></value>" , message );
			sb.Append ( "</member>" );
			sb.Append ( "</struct>" );
			sb.Append ( "</value>" );
			sb.Append ( "</fault>" );
			sb.Append ( "</methodResponse>" );

			context.Response.Write ( sb.ToString ( ) );
		}
	}
}